/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */

#include "BlenderLoadUtil.h"

#include "BlenderDefine.h"
#include "MathUtil.h"

#define BLENDER_VER 249

namespace GNgine
{
	Ogre::Real BlenderLoadUtil::_blenderAnimRate = 25;
	BlenderLoadUtil::BlenderLoadUtil(bParse::bBlenderFile *file) :
			_file(file)
	{
		assert(_file); // Required
	}

	void BlenderLoadUtil::getLayers(Blender::Mesh *mesh, Blender::MTFace **eightLayerArray, Blender::MCol **oneMCol, int &validLayers)
	{
		assert(mesh);

		bParse::bMain *mp= _file->getMain();
		assert(mp);

		validLayers= 0;

		Blender::CustomDataLayer *layers= (Blender::CustomDataLayer*)mesh->fdata.layers;
		if (layers){
			// push valid layers
			for (int i= 0; i < mesh->fdata.totlayer && validLayers < 8; i++){
				if (layers[i].type == CD_MTFACE && eightLayerArray){
					Blender::MTFace *mtf= (Blender::MTFace *)layers[i].data;
					if (mtf)
						eightLayerArray[validLayers++]= mtf;
				}else if (layers[i].type == CD_MCOL){
					if (oneMCol && !(*oneMCol))
						*oneMCol= static_cast<Blender::MCol*>(layers[i].data);
				}
			}
		}else{
			if (eightLayerArray && mesh->mtface){
				eightLayerArray[validLayers++]= mesh->mtface;
			}
			if (oneMCol && mesh->mcol){
				*oneMCol= mesh->mcol;
			}
		}
	}

	int BlenderLoadUtil::getDeformGroupIndex(Blender::Object *ob, const char *group)
	{
		Blender::bDeformGroup *dg, *fg;

		/// named group
		for (dg= (Blender::bDeformGroup*)ob->defbase.first; dg; dg= dg->next)
			if (!strcmp(dg->name, group)) break;

		if (!dg)
			return -1;


		int fidx= 0;
		for (fg= (Blender::bDeformGroup*)ob->defbase.first; fg; fg= fg->next, fidx++)
			if (fg == dg) break;

		return fidx;
	}

	Blender::Material* BlenderLoadUtil::getMaterial(Blender::Object *ob, int index)
	{
		if (!ob || ob->totcol==0) return 0;

		index= PQ3D_Clamp(index, 1, ob->totcol);
		Blender::Material* ma;

		bParse::bMain *mp= _file->getMain();

	#if BLENDER_VER == 249
		/// older files
		if (ob->colbits & (1 << (index-1)))
	#elif BLENDER_VER >= 250
		/// access changed to matbits
		if (ob->matbits[index-1])
	#endif
		{
			ma= (Blender::Material*)ob->mat[index-1];
		}
		else
		{
			Blender::Mesh *me= (Blender::Mesh*)ob->data;
			ma= (Blender::Material*)me->mat[index-1];
		}
		return ma;
	}

	struct CurvePoint
	{
		Ogre::Vector3 h1;
		Ogre::Vector3 cp;
		Ogre::Vector3 h2;
	};


	Ogre::Real BlenderLoadUtil::evaluateIpoCurve(Blender::IpoCurve *icu, const Ogre::Real& time)
	{
		if (!icu->bezt)
			return Ogre::Real(0.0);

		Ogre::Real cval= Ogre::Real(0.0);

		CurvePoint a,b;
		a= *reinterpret_cast<CurvePoint*>(&icu->bezt->vec[0][0]);
		b= *reinterpret_cast<CurvePoint*>(&(icu->bezt+(icu->totvert-1))->vec[0][0]);

		// TODO: Linear, Constant
		if (a.cp.x >= time)
		{
			cval= a.cp.y;
		}
		else if (b.cp.x <= time)
		{
			cval= b.cp.y;
		}
		else
		{
			Blender::BezTriple *ab= icu->bezt;
			Blender::BezTriple *bb= ab + 1;

			for (int i=0; i<icu->totvert; i++)
			{
				if (!ab || !bb) break;
				a= *reinterpret_cast<CurvePoint*>(&ab->vec[0][0]);
				b= *reinterpret_cast<CurvePoint*>(&bb->vec[0][0]);

				if (b.cp.x >= time && a.cp.x <= time)
				{
					if (icu->ipo == IPO_CONST)
						cval= a.cp.y;
					else
					{
						Ogre::Vector2 P0, P1, P2, P3;
						P0= Ogre::Vector2(a.cp.x, a.cp.y);
						P1= Ogre::Vector2(a.h2.x, a.h2.y);
						P2= Ogre::Vector2(b.h1.x, b.h1.y);
						P3= Ogre::Vector2(b.cp.x, b.cp.y);

						Ogre::Real y;
						if (findApproximateCurve(P0, P1, P2, P3, time, y))
						{
							cval= y;
							break;
						}
					}
				}

				ab= bb;
				++bb;
			}
		}
		return cval;
	}

	bool BlenderLoadUtil::findApproximateCurve(Ogre::Vector2 &P0, Ogre::Vector2 &P1, Ogre::Vector2 &P2, Ogre::Vector2& P3, Ogre::Real time, Ogre::Real &cval)
	{
		// Cubic Bezier curves
		// B(t)= (1-t)^3 P0 + 3(1-t)^2 t P1 + 3(1-t)t^2 P2 + t^3 P3, t[0,1]

		// P0 is the start curve point
		// P3 is the end curve point
		// Step along the path to find the closest matching time.
		// false will mean try another set

		cval= Ogre::Real(0.0);
		Ogre::Real s= Ogre::Real(1.0), sc, sst;
		Ogre::Real t= Ogre::Real(0.0), tc, tts;

		//BlenderLoadUtil::CorrectBezier(P0, P1, P2, P3);

		const Ogre::Real step= Ogre::Real(1.0) / _blenderAnimRate;
		while (t <= 1.0)
		{
			sc=  s * s * s;
			tc=  t * t * t;
			sst= Ogre::Real(3.0) * s * s * t;
			tts= Ogre::Real(3.0) * t * t * s;

			if ((sc * P0.x + sst * P1.x + tts * P2.x + tc * P3.x) >= time)
			{
				cval= (sc * P0.y + sst * P1.y + tts * P2.y + tc * P3.y);
				return true;
			}

			t += step;
			s= Ogre::Real(1.0) - t;
		}
		return false;
	}
}
